{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 基本动作\n",
    "\n",
    "欢迎使用JetBot基于浏览器的编程界面！ 该文件是\n",
    "称为* Jupyter Notebook *，它结合了文本，代码和图形\n",
    "展示全部！ 普雷特整洁，是吗？ 如果您不熟悉* Jupyter *，建议您点击\n",
    "顶部工具栏中的``帮助''下拉菜单。 这对\n",
    "使用* Jupyter *进行编程。\n",
    "\n",
    "在本笔记本中，我们将介绍控制JetBot的基础知识。\n",
    "\n",
    "### 导入Robot类\n",
    "\n",
    "要开始对JetBot进行编程，我们需要导入\"Robot\"类。 这节课\n",
    "让我们轻松控制机器人的电机！ 它包含在``jetbot''软件包中。\n",
    "\n",
    ">如果您不熟悉Python， \"package\" 本质上就是一个包含以下内容的文件夹\n",
    ">代码文件。 这些代码文件称为 \"模块 \" 。\n",
    "\n",
    "要导入“Robot”类，请突出显示下面的单元格，然后按“ Ctrl + Enter”或上面的“播放”图标。\n",
    "这将执行单元格中包含的代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from jetbot import Robot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在，我们已经导入了“机器人”类，我们可以按如下所示初始化类\"实例\"。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "robot = Robot()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 给机器人下命令"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在我们已经创建了名为“ robot”的“ Robot”实例，我们可以使用该实例\n",
    "控制机器人。 使机器人以最大速度的30％逆时针旋转\n",
    "我们可以运行下面的单元\n",
    "\n",
    ">警告：下一条命令将使机器人运动！ 请确保机器人有间隙。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "robot.left(speed=0.3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "太酷了，您应该看到机器人逆时针旋转！\n",
    "\n",
    ">如果您的机器人没有向左转，则意味着其中一台电机向后接线！ 尝试关闭电源\n",
    ">机器人并交换不正确电机的``红色''和``黑色''电缆的端子。\n",
    ">\n",
    ">提醒：请务必小心检查接线，不要在正在运行的系统上更改接线！\n",
    "\n",
    "现在，要停止机器人，您可以调用``stop''函数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "robot.stop()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "也许我们只想在设定的时间内运行机器人。 为此，我们可以使用Python的“time”模块包。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "该软件包定义了``sleep''函数，该函数使代码执行在指定的秒数内阻塞\n",
    "在运行下一个命令之前。 请尝试以下操作以使机器人仅左转半秒。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "robot.left(0.3)\n",
    "time.sleep(0.5)\n",
    "robot.stop()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "很好。 您应该看到机器人向左转一点然后停下来。\n",
    "\n",
    ">想知道“ left”方法中的“ speed =”发生了什么？ \n",
    ">Python允许我们通过名称或定义的顺序来设置功能参数\n",
    ">（不指定名称）。\n",
    "\n",
    "``BasicJetbot''类还具有``right''，``forward''和``backwards''方法。 尝试创建自己的单元来制作\n",
    "机器人以50％的速度向前移动一秒钟。\n",
    "\n",
    "通过突出显示现有单元格并按上方的``b''或``+''图标来创建新单元格。 完成此操作后，输入您认为将使机器人以50％的速度向前移动一秒钟的代码。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###单独控制电机\n",
    "\n",
    "上面我们看到了如何使用诸如“ left”，“ right”等命令来控制机器人。但是，如果我们想设置每个电机速度，该怎么办？\n",
    "个别吗？ 好吧，有两种方法可以做到这一点\n",
    "\n",
    "第一种方法是调用set_motors方法。 例如，要向左旋转一秒钟，我们可以将左马达设置为30％，将右马达设置为60％，如下所示。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "robot.set_motors(0.3, 0.6)\n",
    "time.sleep(1.0)\n",
    "robot.stop()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "很好！ 您应该看到机器人沿着左拱形移动。 但是实际上，我们可以通过另一种方式完成同一件事。\n",
    "\n",
    "机器人类具有两个名为left_motor和right_motor的属性，分别代表每个电动机。\n",
    "这些属性是“ Motor”类实例，每个实例都包含一个“ value”属性。 这个``值''属性\n",
    "是一个[traitlet]（https://github.com/ipython/traitlets），在分配新值时会生成“事件”。 电机中\n",
    "类中，我们附加了一个函数，该函数会在值更改时更新电动机命令。\n",
    "\n",
    "因此，要完成与上面完全相同的操作，我们可以执行以下操作。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "robot.left_motor.value = 0.3\n",
    "robot.right_motor.value = 0.6\n",
    "time.sleep(1.0)\n",
    "robot.left_motor.value = 0.0\n",
    "robot.right_motor.value = 0.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "您应该看到机器人以完全相同的方式移动！"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 将电机链接到traitlets"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这些[traitlets]（https://github.com/ipython/traitlets）\n",
    "的一个非常酷的功能是我们还可以将它们链接到其他traitlets！ 这非常方便，因为Jupyter笔记本电脑允许我们\n",
    "制作使用引擎盖下的特征的图形化``小部件''。 这意味着我们可以附加\n",
    "我们的马达变成了“小部件”，可以通过浏览器控制它们，或者只是可视化其价值。\n",
    "\n",
    "为了展示如何做到这一点，让我们创建并显示两个用于控制电机的滑块。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import ipywidgets.widgets as widgets\n",
    "from IPython.display import display\n",
    "\n",
    "# create two sliders with range [-1.0, 1.0]\n",
    "left_slider = widgets.FloatSlider(description='left', min=-1.0, max=1.0, step=0.01, orientation='vertical')\n",
    "right_slider = widgets.FloatSlider(description='right', min=-1.0, max=1.0, step=0.01, orientation='vertical')\n",
    "\n",
    "# create a horizontal box container to place the sliders next to eachother\n",
    "slider_container = widgets.HBox([left_slider, right_slider])\n",
    "\n",
    "# display the container in this cell's output\n",
    "display(slider_container)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "您应该看到上面显示了两个“垂直”滑块。\n",
    "\n",
    ">提示：在Jupyter Lab中，您实际上可以将单元格的输出“弹出”到完全独立的窗口中！ \n",
    ">仍然会连接到笔记本电脑，但单独显示。 如果我们想固定在其他地方执行的代码输出，这将很有帮助。\n",
    ">为此，右键单击单元格的输出并选择``为输出创建新视图''。 然后可以拖动新窗口\n",
    ">到您喜欢的位置。\n",
    "\n",
    "尝试单击并上下拖动滑块。 注意，当我们当前移动滑块时，什么也没有发生。 那是因为我们还没有将它们连接到电动机！ 我们将使用traitlets包中的``link''函数来做到这一点。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import traitlets\n",
    "\n",
    "left_link = traitlets.link((left_slider, 'value'), (robot.left_motor, 'value'))\n",
    "right_link = traitlets.link((right_slider, 'value'), (robot.right_motor, 'value'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在尝试拖动滑块（首先缓慢拖动）。 您应该看到相应的电机转向！\n",
    "\n",
    "我们在上面创建的``link''函数实际上创建了双向链接！ 就是说\n",
    "如果我们在其他地方设置电机值，滑块将更新！ 尝试执行下面的代码块"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "robot.forward(0.3)\n",
    "time.sleep(1.0)\n",
    "robot.stop()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "您应该看到滑块响应电动机命令！ 如果要删除此连接，可以致电\n",
    "每个链接的``unlink''方法。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "left_link.unlink()\n",
    "right_link.unlink()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "但是，如果我们不希望使用“双向”链接，那我们只想使用滑块来显示电机值，\n",
    "但不能控制他们。 为此，我们可以使用dlink函数。 左输入是\"源\"，右输入是\"目标\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "left_link = traitlets.dlink((robot.left_motor, 'value'), (left_slider, 'value'))\n",
    "right_link = traitlets.dlink((robot.right_motor, 'value'), (right_slider, 'value'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在尝试移动滑块。 您应该看到机器人没有响应。 但是，当使用其他方法设置电机时，\n",
    "滑块将更新并显示值！"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 将功能附加到事件"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "使用特质的另一种方法是将函数（例如``forward''）附加到事件上。 这些\n",
    "每当对象发生更改时，函数就会被调用，并将传递有关该更改的一些信息\n",
    "例如“旧”值和“新”值。\n",
    "\n",
    "让我们创建并显示一些用于控制机器人的按钮。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# create buttons\n",
    "button_layout = widgets.Layout(width='100px', height='80px', align_self='center')\n",
    "stop_button = widgets.Button(description='stop', button_style='danger', layout=button_layout)\n",
    "forward_button = widgets.Button(description='forward', layout=button_layout)\n",
    "backward_button = widgets.Button(description='backward', layout=button_layout)\n",
    "left_button = widgets.Button(description='left', layout=button_layout)\n",
    "right_button = widgets.Button(description='right', layout=button_layout)\n",
    "\n",
    "# display buttons\n",
    "middle_box = widgets.HBox([left_button, stop_button, right_button], layout=widgets.Layout(align_self='center'))\n",
    "controls_box = widgets.VBox([forward_button, middle_box, backward_button])\n",
    "display(controls_box)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "您应该看到上面显示了一组机器人控件！ 但是现在他们什么也不会做。 要做到这一点\n",
    "我们需要创建一些函数，并将其附加到按钮的on_click事件。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def stop(change):\n",
    "    robot.stop()\n",
    "    \n",
    "def step_forward(change):\n",
    "    robot.forward(0.4)\n",
    "    time.sleep(0.5)\n",
    "    robot.stop()\n",
    "\n",
    "def step_backward(change):\n",
    "    robot.backward(0.4)\n",
    "    time.sleep(0.5)\n",
    "    robot.stop()\n",
    "\n",
    "def step_left(change):\n",
    "    robot.left(0.3)\n",
    "    time.sleep(0.5)\n",
    "    robot.stop()\n",
    "\n",
    "def step_right(change):\n",
    "    robot.right(0.3)\n",
    "    time.sleep(0.5)\n",
    "    robot.stop()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在我们已经定义了函数，让我们将它们附加到每个按钮的单击事件上"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# link buttons to actions\n",
    "stop_button.on_click(stop)\n",
    "forward_button.on_click(step_forward)\n",
    "backward_button.on_click(step_backward)\n",
    "left_button.on_click(step_left)\n",
    "right_button.on_click(step_right)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "现在，当您单击每个按钮时，您应该看到机器人在移动！"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 心跳停止开关\n",
    "\n",
    "在这里，我们展示了如何连接“心跳(heartbeat)”以阻止机器人运动。 这是检测机械手连接是否有效的简单方法。 您可以降低下面的滑块以减少心跳的时间（以秒为单位）。 如果无法在两个心跳之间进行浏览器之间的往返通信，则心跳的“ status”属性将设置为“死”。 恢复连接后，status属性将恢复为alive。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from jetbot import Heartbeat\n",
    "\n",
    "heartbeat = Heartbeat()\n",
    "\n",
    "# this function will be called when heartbeat 'alive' status changes\n",
    "def handle_heartbeat_status(change):\n",
    "    if change['new'] == Heartbeat.Status.dead:\n",
    "        robot.stop()\n",
    "        \n",
    "heartbeat.observe(handle_heartbeat_status, names='status')\n",
    "\n",
    "period_slider = widgets.FloatSlider(description='period', min=0.001, max=0.5, step=0.01, value=0.5)\n",
    "traitlets.dlink((period_slider, 'value'), (heartbeat, 'period'))\n",
    "\n",
    "display(period_slider, heartbeat.pulseout)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "尝试执行以下代码来启动电动机，然后放下滑块以查看会发生什么。 您也可以尝试断开机器人或PC的连接。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "robot.left(0.2) \n",
    "\n",
    "# now lower the `period` slider above until the network heartbeat can't be satisfied"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 结论\n",
    "\n",
    "\n",
    "120/5000\n",
    "本示例就是这样！ 希望您有信心可以对机器人进行编程以使其立即移动 :)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
